//	VirtualDub - Video processing and capture application
//	System library component
//	Copyright (C) 1998-2004 Avery Lee, All Rights Reserved.
//
//	Beginning with 1.6.0, the VirtualDub system library is licensed
//	differently than the remainder of VirtualDub.  This particular file is
//	thus licensed as follows (the "zlib" license):
//
//	This software is provided 'as-is', without any express or implied
//	warranty.  In no event will the authors be held liable for any
//	damages arising from the use of this software.
//
//	Permission is granted to anyone to use this software for any purpose,
//	including commercial applications, and to alter it and redistribute it
//	freely, subject to the following restrictions:
//
//	1.	The origin of this software must not be misrepresented; you must
//		not claim that you wrote the original software. If you use this
//		software in a product, an acknowledgment in the product
//		documentation would be appreciated but is not required.
//	2.	Altered source versions must be plainly marked as such, and must
//		not be misrepresented as being the original software.
//	3.	This notice may not be removed or altered from any source
//		distribution.

#include "stdafx.h"
#include <windows.h>

#include <vd2/system/error.h>
#include <vd2/system/filesys.h>
#include <vd2/system/VDString.h>
#include <vd2/system/file.h>

namespace {
	bool IsWindowsNT() {
		static bool sbIsNT = (LONG)GetVersion()>=0;
		return sbIsNT;
	}

	bool IsHardDrivePath(const wchar_t *path) {
		const VDStringW rootPath(VDFileGetRootPath(path));

		UINT type = GetDriveTypeW(rootPath.c_str());

		return type == DRIVE_FIXED || type == DRIVE_UNKNOWN || type == DRIVE_REMOVABLE;
	}
};

///////////////////////////////////////////////////////////////////////////////
//
//	VDFile
//
///////////////////////////////////////////////////////////////////////////////

using namespace nsVDFile;

VDFile::VDFile(const char *pszFileName, uint32 flags)
	: mhFile(NULL)
{
	open_internal(pszFileName, NULL, flags);
}

VDFile::VDFile(const wchar_t *pwszFileName, uint32 flags)
	: mhFile(NULL)
{
	open_internal(NULL, pwszFileName, flags);
}

VDFile::VDFile(HANDLE h)
	: mhFile(h)
{
	LONG lo, hi = 0;

	lo = SetFilePointer(h, 0, &hi, FILE_CURRENT);

	mFilePosition = (uint32)lo + ((uint64)(uint32)hi << 32);
}

VDFile::~VDFile() {
	closeNT();
}

void VDFile::open(const char *pszFilename, uint32 flags) {
	open_internal(pszFilename, NULL, flags);
}

void VDFile::open(const wchar_t *pwszFilename, uint32 flags) {
	open_internal(NULL, pwszFilename, flags);
}

void VDFile::open_internal(const char *pszFilename, const wchar_t *pwszFilename, uint32 flags) {
	close();

	mpFilename = _wcsdup(VDFileSplitPath(pszFilename ? VDTextAToW(pszFilename).c_str() : pwszFilename));
	if (!mpFilename)
		throw MyMemoryError();

	// At least one of the read/write flags must be set.
	VDASSERT(flags & (kRead | kWrite));

	DWORD dwDesiredAccess = 0;

	if (flags & kRead)  dwDesiredAccess  = GENERIC_READ;
	if (flags & kWrite) dwDesiredAccess |= GENERIC_WRITE;

	// Win32 docs are screwed here -- FILE_SHARE_xxx is the inverse of a deny flag.

	DWORD dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
	if (flags & kDenyRead)	dwShareMode = FILE_SHARE_WRITE;
	if (flags & kDenyWrite) dwShareMode &= ~FILE_SHARE_WRITE;

	// One of the creation flags must be set.
	VDASSERT(flags & kCreationMask);

	DWORD dwCreationDisposition;

	uint32 creationType = flags & kCreationMask;

	switch(creationType) {
	case kOpenExisting:		dwCreationDisposition = OPEN_EXISTING; break;
	case kOpenAlways:		dwCreationDisposition = OPEN_ALWAYS; break;
	case kCreateAlways:		dwCreationDisposition = CREATE_ALWAYS; break;
	case kCreateNew:		dwCreationDisposition = CREATE_NEW; break;
	case kTruncateExisting:	dwCreationDisposition = TRUNCATE_EXISTING; break;
	default:
		VDNEVERHERE;
		return;
	}

	VDASSERT((flags & (kSequential | kRandomAccess)) != (kSequential | kRandomAccess));

	DWORD dwAttributes = FILE_ATTRIBUTE_NORMAL;

	if (flags & kSequential)	dwAttributes |= FILE_FLAG_SEQUENTIAL_SCAN;
	if (flags & kRandomAccess)	dwAttributes |= FILE_FLAG_RANDOM_ACCESS;
	if (flags & kWriteThrough)	dwAttributes |= FILE_FLAG_WRITE_THROUGH;
	if (flags & kUnbuffered)	dwAttributes |= FILE_FLAG_NO_BUFFERING;

	VDStringA tempFilenameA;
	VDStringW tempFilenameW;

	if (IsWindowsNT()) {
		if (pszFilename) {
			tempFilenameW = VDTextAToW(pszFilename);
			pwszFilename = tempFilenameW.c_str();
			pszFilename = NULL;
		}
	} else {
		if (pwszFilename) {
			tempFilenameA = VDTextWToA(pwszFilename);
			pszFilename = tempFilenameA.c_str();
			pwszFilename = NULL;
		}
	}

	if (pszFilename)
		mhFile = CreateFileA(pszFilename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwAttributes, NULL);
	else {
		if (!IsHardDrivePath(pwszFilename))
			flags &= ~FILE_FLAG_NO_BUFFERING;

		mhFile = CreateFileW(pwszFilename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwAttributes, NULL);
	}

	DWORD err = GetLastError();

	// If we failed and FILE_FLAG_NO_BUFFERING was set, strip it and try again.
	// VPC and Novell shares sometimes do this....
	if (mhFile == INVALID_HANDLE_VALUE && err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) {
		if (dwAttributes & FILE_FLAG_NO_BUFFERING) {
			dwAttributes &= ~FILE_FLAG_NO_BUFFERING;
			dwAttributes |= FILE_FLAG_WRITE_THROUGH;

			if (pszFilename)
				mhFile = CreateFileA(pszFilename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwAttributes, NULL);
			else
				mhFile = CreateFileW(pwszFilename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwAttributes, NULL);

			err = GetLastError();
		}
	}

	// INVALID_HANDLE_VALUE isn't NULL.  *sigh*

	if (mhFile == INVALID_HANDLE_VALUE) {
		mhFile = NULL;

		throw MyWin32Error("Cannot open file \"%ls\":\n%%s", err, mpFilename.get());
	}

	mFilePosition = 0;
}

bool VDFile::closeNT() {
	if (mhFile) {
		HANDLE h = mhFile;
		mhFile = NULL;
		if (!CloseHandle(h))
			return false;
	}

	return true;
}

void VDFile::close() {
	if (!closeNT())
		throw MyWin32Error("Cannot complete file \"%ls\": %%s", GetLastError(), mpFilename.get());
}

bool VDFile::truncateNT() {
	return 0 != SetEndOfFile(mhFile);
}

void VDFile::truncate() {
	if (!truncateNT())
		throw MyWin32Error("Cannot truncate file \"%ls\": %%s", GetLastError(), mpFilename.get());
}

bool VDFile::extendValidNT(sint64 pos) {
	if (GetVersion() & 0x80000000)
		return true;				// No need, Windows 95/98/ME do this automatically anyway.

	// The SetFileValidData() API is only available on XP and Server 2003.

	typedef BOOL (APIENTRY *tpSetFileValidData)(HANDLE hFile, LONGLONG ValidDataLength);		// Windows XP, Server 2003
	static tpSetFileValidData pSetFileValidData = (tpSetFileValidData)GetProcAddress(GetModuleHandle("kernel32"), "SetFileValidData");

	if (!pSetFileValidData) {
		SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
		return false;
	}

	return 0 != pSetFileValidData(mhFile, pos);
}

void VDFile::extendValid(sint64 pos) {
	if (!extendValidNT(pos))
		throw MyWin32Error("Cannot extend file \"%ls\": %%s", GetLastError(), mpFilename.get());
}

bool VDFile::enableExtendValid() {
	if (GetVersion() & 0x80000000)
		return true;				// Not Windows NT, no privileges involved

	// SetFileValidData() requires the SE_MANAGE_VOLUME_NAME privilege, so we must enable it
	// on the process token. We don't attempt to strip the privilege afterward as that would
	// introduce race conditions.
	bool bSuccessful = false;
	DWORD err = 0;

	SetLastError(0);

	HANDLE h;
	if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &h)) {
		LUID luid;

		if (LookupPrivilegeValue(NULL, SE_MANAGE_VOLUME_NAME, &luid)) {
			TOKEN_PRIVILEGES tp;
			tp.PrivilegeCount = 1;
			tp.Privileges[0].Luid = luid;
			tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

			if (AdjustTokenPrivileges(h, FALSE, &tp, 0, NULL, NULL))
				bSuccessful = true;
			else
				err = GetLastError();
		}

		CloseHandle(h);
	}

	if (!bSuccessful && err)
		SetLastError(err);

	return bSuccessful;
}

long VDFile::readData(void *buffer, long length) {
	DWORD dwActual;

	if (!ReadFile(mhFile, buffer, (DWORD)length, &dwActual, NULL))
		throw MyWin32Error("Cannot read from file \"%ls\": %%s", GetLastError(), mpFilename.get());

	mFilePosition += dwActual;

	return dwActual;
}

void VDFile::read(void *buffer, long length) {
	if (length != readData(buffer, length))
		throw MyWin32Error("Cannot read from file \"%ls\": Premature end of file.", GetLastError(), mpFilename.get());
}

long VDFile::writeData(const void *buffer, long length) {
	DWORD dwActual;
	bool success = false;

	if (!WriteFile(mhFile, buffer, (DWORD)length, &dwActual, NULL) || dwActual != (DWORD)length)
		goto found_error;

	mFilePosition += dwActual;

	return dwActual;

found_error:
	throw MyWin32Error("Cannot write to file \"%ls\": %%s", GetLastError(), mpFilename.get());
}

void VDFile::write(const void *buffer, long length) {
	if (length != writeData(buffer, length))
		throw MyWin32Error("Cannot write to file \"%ls\": Unable to write all data.", GetLastError(), mpFilename.get());
}

bool VDFile::seekNT(sint64 newPos, eSeekMode mode) {
	DWORD dwMode;

	switch(mode) {
	case kSeekStart:
		dwMode = FILE_BEGIN;
		break;
	case kSeekCur:
		dwMode = FILE_CURRENT;
		break;
	case kSeekEnd:
		dwMode = FILE_END;
		break;
	default:
		VDNEVERHERE;
		return false;
	}

	union {
		sint64 pos;
		LONG l[2];
	} u = { newPos };

	u.l[0] = SetFilePointer(mhFile, u.l[0], &u.l[1], dwMode);

	if (u.l[0] == -1 && GetLastError() != NO_ERROR)
		return false;

	mFilePosition = u.pos;
	return true;
}

void VDFile::seek(sint64 newPos, eSeekMode mode) {
	if (!seekNT(newPos, mode))
		throw MyWin32Error("Cannot seek within file \"%ls\": %%s", GetLastError(), mpFilename.get());
}

bool VDFile::skipNT(sint64 delta) {
	if (!delta)
		return true;

	char buf[1024];

	if (delta <= sizeof buf) {
		return (long)delta == readData(buf, (long)delta);
	} else
		return seekNT(delta, kSeekCur);
}

void VDFile::skip(sint64 delta) {
	if (!delta)
		return;

	char buf[1024];

	if (delta <= sizeof buf) {
		if ((long)delta != readData(buf, (long)delta))
			throw MyWin32Error("Cannot seek within file \"%ls\": %%s", GetLastError(), mpFilename.get());
	} else
		seek(delta, kSeekCur);
}

sint64 VDFile::size() {
	union {
		uint64 siz;
		DWORD l[2];
	} u;

	u.l[0] = GetFileSize(mhFile, &u.l[1]);

	DWORD err;

	if (u.l[0] == (DWORD)-1L && (err = GetLastError()) != NO_ERROR)
		throw MyWin32Error("Cannot retrieve size of file \"%ls\": %%s", GetLastError(), mpFilename.get());

	return (sint64)u.siz;
}

sint64 VDFile::tell() {
	return mFilePosition;
}

bool VDFile::isOpen() {
	return mhFile != 0;
}

VDFileHandle VDFile::getRawHandle() {
	return mhFile;
}

void *VDFile::AllocUnbuffer(size_t nBytes) {
	return VirtualAlloc(NULL, nBytes, MEM_COMMIT, PAGE_READWRITE);
}

void VDFile::FreeUnbuffer(void *p) {
	VirtualFree(p, 0, MEM_RELEASE);
}

///////////////////////////////////////////////////////////////////////////////

VDFileStream::~VDFileStream() {
}

const wchar_t *VDFileStream::GetNameForError() {
	return getFilenameForError();
}

sint64 VDFileStream::Pos() {
	return tell();
}

void VDFileStream::Read(void *buffer, sint32 bytes) {
	read(buffer, bytes);
}

sint32 VDFileStream::ReadData(void *buffer, sint32 bytes) {
	return readData(buffer, bytes);
}

sint64 VDFileStream::Length() {
	return size();
}

void VDFileStream::Seek(sint64 offset) {
	seek(offset);
}

///////////////////////////////////////////////////////////////////////////////

VDMemoryStream::VDMemoryStream(const void *pSrc, uint32 len) 
	: mpSrc((const char *)pSrc)
	, mPos(0)
	, mLength(len)
{
}

const wchar_t *VDMemoryStream::GetNameForError() {
	return L"memory stream";
}

sint64 VDMemoryStream::Pos() {
	return mPos;
}

void VDMemoryStream::Read(void *buffer, sint32 bytes) {
	if (bytes != ReadData(buffer, bytes))
		throw MyError("Attempt to read beyond stream.");
}

sint32 VDMemoryStream::ReadData(void *buffer, sint32 bytes) {
	if (bytes <= 0)
		return 0;

	if (bytes + mPos > mLength)
		bytes = mLength - mPos;

	if (bytes > 0) {
		memcpy(buffer, mpSrc+mPos, bytes);
		mPos += bytes;
	}

	return bytes;
}

sint64 VDMemoryStream::Length() {
	return mLength;
}

void VDMemoryStream::Seek(sint64 offset) {
	if (offset < 0 || offset > mLength)
		throw MyError("Invalid seek position");

	mPos = (uint32)offset;
}

///////////////////////////////////////////////////////////////////////////////

VDBufferedStream::VDBufferedStream(IVDRandomAccessStream *pSrc, uint32 bufferSize)
	: mpSrc(pSrc)
	, mBuffer(bufferSize)
	, mBasePosition(0)
	, mBufferOffset(0)
	, mBufferValidSize(0)
{
}

VDBufferedStream::~VDBufferedStream() {
}

const wchar_t *VDBufferedStream::GetNameForError() {
	return mpSrc->GetNameForError();
}

sint64 VDBufferedStream::Pos() {
	return mBasePosition + mBufferOffset;
}

void VDBufferedStream::Read(void *buffer, sint32 bytes) {
	if (bytes != ReadData(buffer, bytes))
		throw MyError("Cannot read %d bytes at location %08llx from %ls", mpSrc->GetNameForError());
}

sint32 VDBufferedStream::ReadData(void *buffer, sint32 bytes) {
	if (bytes <= 0)
		return 0;

	uint32 actual = 0;
	for(;;) {
		uint32 tc = mBufferValidSize - mBufferOffset;

		if (tc > (uint32)bytes)
			tc = (uint32)bytes;

		if (tc) {
			if (buffer) {
				memcpy(buffer, mBuffer.data() + mBufferOffset, tc);
				buffer = (char *)buffer + tc;
			}

			mBufferOffset += tc;
			bytes -= tc;
			actual += tc;

			if (!bytes)
				break;
		}

		// At this point, the buffer is empty.
		if (mBufferValidSize) {
			VDASSERT(mBufferOffset >= mBufferValidSize);

			mBasePosition += mBufferValidSize;
			mBufferOffset = 0;
			mBufferValidSize = 0;
		}

		// If the remaining read is large, issue it directly to the underlying stream.
		if (buffer && (uint32)bytes >= mBuffer.size() * 2) {
			sint32 localActual = mpSrc->ReadData(buffer, bytes);
			mBasePosition += localActual;
			actual += localActual;
			break;
		}

		// Refill the buffer.
		mBufferValidSize = mpSrc->ReadData(mBuffer.data(), mBuffer.size());
		mBufferOffset = 0;
		if (!mBufferValidSize)
			break;
	}

	return actual;
}

sint64 VDBufferedStream::Length() {
	return mpSrc->Length();
}

void VDBufferedStream::Seek(sint64 offset) {
	// check if an in-buffer skip is possible
	sint64 relativeOffset = offset - mBasePosition;
	if (relativeOffset >= 0 && relativeOffset <= (sint64)mBufferValidSize) {
		mBufferOffset = (uint32)relativeOffset;
		return;
	}

	// flush buffer
	mBufferOffset = 0;
	mBufferValidSize = 0;

	// issue seek
	mpSrc->Seek(offset);
	mBasePosition = offset;
}

void VDBufferedStream::Skip(sint64 size) {
	sint64 targetPos = mBasePosition + mBufferOffset + size;
	sint64 bufferEnd = mBasePosition + mBufferValidSize;

	// check if we can do a buffered skip
	if (targetPos >= bufferEnd && targetPos < bufferEnd + (sint64)mBuffer.size()) {
		Read(NULL, (sint32)size);
		return;
	}

	// issue a seek
	Seek(targetPos);
}

///////////////////////////////////////////////////////////////////////////////

VDTextStream::VDTextStream(IVDStream *pSrc)
	: mpSrc(pSrc)
	, mBufferPos(0)
	, mBufferLimit(0)
	, mState(kFetchLine)
	, mFileBuffer(kFileBufferSize)
{
}

VDTextStream::~VDTextStream() {
}

const char *VDTextStream::GetNextLine() {
	if (!mpSrc)
		return NULL;

	mLineBuffer.clear();

	for(;;) {
		if (mBufferPos >= mBufferLimit) {
			mBufferPos = 0;
			mBufferLimit = mpSrc->ReadData(mFileBuffer.data(), mFileBuffer.size());

			if (!mBufferLimit) {
				mpSrc = NULL;

				if (mLineBuffer.empty())
					return NULL;

				mLineBuffer.push_back(0);

				return mLineBuffer.data();
			}
		}

		switch(mState) {

			case kEatNextIfCR:
				mState = kFetchLine;
				if (mFileBuffer[mBufferPos] == '\r')
					++mBufferPos;
				continue;

			case kEatNextIfLF:
				mState = kFetchLine;
				if (mFileBuffer[mBufferPos] == '\n')
					++mBufferPos;
				continue;

			case kFetchLine:
				uint32 base = mBufferPos;

				do {
					const char c = mFileBuffer[mBufferPos++];

					if (c == '\r') {
						mState = kEatNextIfLF;
						mLineBuffer.insert(mLineBuffer.end(), mFileBuffer.begin() + base, mFileBuffer.begin() + (mBufferPos-1));
						mLineBuffer.push_back(0);
						return mLineBuffer.data();
					}
					if (c == '\n') {
						mState = kEatNextIfCR;
						mLineBuffer.insert(mLineBuffer.end(), mFileBuffer.begin() + base, mFileBuffer.begin() + (mBufferPos-1));
						mLineBuffer.push_back(0);
						return mLineBuffer.data();
					}
				} while(mBufferPos < mBufferLimit);
				mLineBuffer.insert(mLineBuffer.end(), mFileBuffer.begin() + base, mFileBuffer.begin() + mBufferLimit);
				break;
		}
	}
}

///////////////////////////////////////////////////////////////////////////////

VDTextInputFile::VDTextInputFile(const wchar_t *filename, uint32 flags)
	: mFileStream(filename, flags | nsVDFile::kRead)
	, mTextStream(&mFileStream)
{
}

VDTextInputFile::~VDTextInputFile() {
}

///////////////////////////////////////////////////////////////////////////////

VDTextOutputFile::VDTextOutputFile(const wchar_t *filename, uint32 flags)
	: mFile(filename, flags | nsVDFile::kWrite)
	, mLevel(0)
{
}

VDTextOutputFile::~VDTextOutputFile() {
	try { 
		Close();
	} catch(const MyError&) {
		// ignore errors in destructor
	}
}

void VDTextOutputFile::Close() {
	if (mFile.isOpen()) {
		if (mLevel)
			mFile.write(mBuf, mLevel);
		mFile.close();
	}
}

void VDTextOutputFile::PutLine(const char *s) {
	PutData(s, strlen(s));
	PutData("\r\n", 2);
}

void VDTextOutputFile::FormatLine(const char *format, ...) {
	va_list val;

	va_start(val, format);

	int rv = -1;
	if (mLevel < kBufSize-4)
		rv = _vsnprintf(mBuf+mLevel, kBufSize-mLevel, format, val);

	if (rv >= 0)
		mLevel += rv;
	else
		FormatLine2(format, val);

	PutData("\r\n", 2);
	va_end(val);
}

void VDTextOutputFile::FormatLine2(const char *format, va_list val) {
	char buf[3072];

	int rv = _vsnprintf(buf, 3072, format, val);
	if (rv > 0)
		PutData(buf, rv);
}

void VDTextOutputFile::PutData(const char *s, int len) {
	while(len > 0) {
		int left = kBufSize - mLevel;
		if (!left) {
			mFile.write(mBuf, kBufSize);
			mLevel = 0;
			left = kBufSize;
		}

		int tc = len;

		if (tc > left)
			tc = left;

		memcpy(mBuf + mLevel, s, tc);

		s += tc;
		len -= tc;
		mLevel += tc;
	}
}
